/*
 * Copyright 2006 Matt Jensen
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.spectaql.internal.query;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.spectaql.CollectionConstraintSetters;
import org.spectaql.ConstraintSetters;
import org.spectaql.QueryContext;
import org.spectaql.internal.SpectaqlContextInternal;
import org.spectaql.metadata.PropertyMetadata;

import java.util.*;

/**
 * <p>
 * <code>QueryContextImpl</code> is the internal implementation of the {@link QueryContext} interface. It provides all
 * of the facilities necessary to build a single dynamic query. 
 * </p>
 * <p>
 * <strong>Thread Safety:</strong> Instances of this class must be created and accessed from within a single thread.
 * </p>
 */
public class QueryContextImpl implements QueryContext, QueryContextInternal
{
    private static Log LOG = LogFactory.getLog(QueryContextImpl.class);

    /**
     * <p>
     * The owning Spectaql context. 
     * </p>
     */
    private SpectaqlContextInternal m_spectaqlContext;

    /**
     * <p>
     * Matcher entity property reference queue. 
     * </p>
     */
    private Queue<PropertyReferenceCapture> m_propertyReferences = new LinkedList<PropertyReferenceCapture>();

    /**
     * <p>
     * Construct a {@link QueryContextImpl} instance.
     * </p>
     *
     * @param spectaqlContext the owning context.
     */
    public QueryContextImpl(final SpectaqlContextInternal spectaqlContext)
    {
        super();
        assert null != spectaqlContext : "The [owningContext] argument cannot be null.";

        m_spectaqlContext = spectaqlContext;
    }

    /* inherit javadoc */
    public <T> T matcher(final Class<T> clazz) throws IllegalArgumentException
    {
        // We can only create matchers for mapped entity classes.
        if (!m_spectaqlContext.isEntityClass(clazz))
        {
            throw new IllegalArgumentException(String.format(
                    "Cannot create matcher for class [%s] because it is not a mapped entity class.", clazz));
        }

        // Create the matcher.
        return m_spectaqlContext.createProxy(clazz, new MatcherProxy(this));
    }

    /* inherit javadoc */
    public <T> ConstraintSetters<T> constrain(final T propertyReference)
    {
        final PropertyReferenceCapture propertyReferenceCapture = m_propertyReferences.remove();
        return null;
    }

    /* inherit javadoc */
    public <T> CollectionConstraintSetters<T> constrain(final Collection<T> propertyReference)
    {
        final PropertyReferenceCapture propertyReferenceCapture = m_propertyReferences.remove();
        return null;
    }

    /* inherit javadoc */
    public void addPropertyReference(final PropertyMetadata propertyMetadata)
    {
        m_propertyReferences.add(new PropertyReferenceCapture(propertyMetadata));
    }

    /* inherit javadoc */
    public void addSubPropertyReference(final PropertyMetadata propertyMetadata)
    {
        if (m_propertyReferences.isEmpty())
        {
            throw new IllegalStateException();
        }
        m_propertyReferences.peek().addSubPropertyReference(propertyMetadata);
    }

    /* inherit javadoc */
    public SpectaqlContextInternal getSpectaqlContext()
    {
        return m_spectaqlContext;
    }

    /**
     * <p>
     * <code>PropertyReferenceCapture</code> is used to capture information on the invocation of a matcher entity
     * property accessor method.
     * </p>
     */
    private static class PropertyReferenceCapture
    {
        /**
         * <p>
         * The property metadata. 
         * </p>
         */
        private final PropertyMetadata m_propertyMetadata;

        /**
         * <p>
         * List representing the sub-properties which are part of the reference, if any. 
         * </p>
         */
        private List<PropertyMetadata> m_subPropertyMetadata;

        /**
         * <p>
         * Construct a {@link PropertyReferenceCapture} instance. 
         * </p>
         *
         * @param propertyMetadata the property metadata for the property which was accessed.
         */
        PropertyReferenceCapture(final PropertyMetadata propertyMetadata)
        {
            super();
            assert null != propertyMetadata : "The [propertyMetadata] argument cannot be null.";

            m_propertyMetadata = propertyMetadata;
        }

        /**
         * <p>
         * Add a sub-property reference.
         * </p>
         *
         * @param propertyMetadata the sub-property metadata.
         */
        void addSubPropertyReference(final PropertyMetadata propertyMetadata)
        {
            if (null == m_subPropertyMetadata)
            {
                m_subPropertyMetadata = new ArrayList<PropertyMetadata>();
            }
            m_subPropertyMetadata.add(propertyMetadata);
        }

        PropertyMetadata getPropertyMetadata()
        {
            return m_propertyMetadata;
        }

        List<PropertyMetadata> getSubPropertyMetadata()
        {
            return m_subPropertyMetadata;
        }
    }
}
